/*******************************************************************************
 * Copyright 2011 @ Kapil Viren Ahuja
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.kapil.framework.database.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;

import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

import com.kapil.framework.database.IDatabaseSetup;
import com.kapil.framework.exception.impl.ConfigurationException;
import com.kapil.framework.exception.impl.ObjectInitializationException;
import com.kapil.framework.lang.StringUtils;
import com.kapil.framework.logger.ILogger;
import com.kapil.framework.logger.LogFactory;


/**
 * <p>
 * This is an implementation of {@link IDatabaseSetup} providing the capabilities to setup a database based on
 * externalised query files.
 * </p>
 * 
 * <p>
 * This implementation uses Spring's {@link JdbcDaoSupport} to interact with the database. It needs the script file
 * either in a fully qualified disk location or in a class path. One of these parameters are necessary for the database
 * setup. User can provide a list of file names which will read from the <code>classPathLocation</code> or the
 * <code>diskLocation</code>.
 * </p>
 * 
 */
public abstract class AbstractDatabaseSetup extends JdbcDaoSupport implements IDatabaseSetup
{

    /** The Constant logger. */
    private static final ILogger logger = LogFactory.getInstance().getLogger(AbstractDatabaseSetup.class);

    /** The disk location. */
    private String               diskLocation;

    /** The class path location. */
    private String               classPathLocation;

    /** The file names. */
    private List<String>         fileNames;


    /**
     * Sets the disk location.
     * 
     * @param diskLocation the new disk location
     */
    public void setDiskLocation(String diskLocation)
    {
        this.diskLocation = diskLocation;
    }


    /**
     * Gets the file names.
     * 
     * @return the file names
     */
    public List<String> getFileNames()
    {
        return fileNames;
    }


    /**
     * Sets the file names.
     * 
     * @param fileNames the new file names
     */
    public void setFileNames(List<String> fileNames)
    {
        this.fileNames = fileNames;
    }


    /**
     * Sets the class path location.
     * 
     * @param classPathLocation the new class path location
     */
    public void setClassPathLocation(String classPathLocation)
    {
        this.classPathLocation = classPathLocation;
    }


    /**
     * Executes all the listed queries on the database. Queries are executed in the same order in which they are listed.
     */
    public void setupDatabase()
    {
        if (getFileNames() == null)
        {
            throw new IllegalArgumentException("You have to provide at least one file.");
        }
        else if (classPathLocation != null)
        {
            for (String fileName : this.fileNames)
            {
                String filePath = classPathLocation + "/" + fileName;
                ClassPathResource resource = new ClassPathResource(filePath);
                File file = null;
                try
                {
                    file = resource.getFile();
                    executeFile(file);
                }
                catch (ConfigurationException cex)
                {
                    throw cex;
                }
                catch (Exception ex)
                {
                    logger.error("Could not load file ##, from classpath ##", new String[] { fileName, filePath });
                    throw new ObjectInitializationException(ObjectInitializationException.CLASS_NOT_FOUND, new String[] { fileName + " at " + filePath });
                }
            }
        }
        else if (diskLocation != null)
        {
            for (String fileName : this.fileNames)
            {
                String filePath = diskLocation + "/" + fileName;
                File file = new File(filePath);
                try
                {
                    executeFile(file);
                }
                catch (ConfigurationException cex)
                {
                    throw cex;
                }
                catch (Exception e)
                {
                    logger.error("Could not load file ##, from classpath ##", new String[] { fileName, filePath });
                    throw new ObjectInitializationException(ObjectInitializationException.CLASS_NOT_FOUND, new String[] { fileName + " at " + filePath }, e);
                }

            }
        }
        else
        {
            throw new IllegalArgumentException("You have to provide at least one location.");
        }
    }


    /**
     * Reads a query from a file and executes on the database.
     * 
     * @param file the file
     * @throws IOException Signals that an I/O exception has occurred.
     */
    protected void executeFile(File file) throws IOException
    {
        String query = null;
        BufferedReader br = null;

        try
        {
            br = new BufferedReader(new FileReader(file));
            query = readFile(br);
        }
        catch (FileNotFoundException fnfex)
        {
            logger.error("Could not load file ##, from classpath ##", new String[] { file.getName(), file.getAbsolutePath() });
        }
        finally
        {
            br.close();
        }

        if (StringUtils.isNotBlank(query))
        {
            exeQuery(query);
        }
    }


    /**
     * Reads a file line by line and returns in a single string.
     * 
     * @param reader the reader
     * @return the string
     */
    abstract public String readFile(BufferedReader reader);


    /**
     * Executes the query on the database passed as a Parameter.
     * 
     * @param query the query
     */
    protected void exeQuery(String query)
    {
        try
        {
            getJdbcTemplate().execute(query);
        }
        catch (Exception ex)
        {
            if (org.apache.commons.lang.StringUtils.contains(ex.getMessage(), "Could not get JDBC Connection"))
            {
                throw new ConfigurationException(ConfigurationException.DATABASE_CONNECTION_REFUSED, new String[] {ex.toString()});
            }
        }
    }
}
